//
// Created by pc on 2022/5/21.
//

#include "String.h"
#include <cstring>


String::Srep::Srep(int len, const char *p) {
    ref_num = 1;
    size = len;
    str = new char[size + 1];
    strcpy(str, p);
}

//构造函数就是给rep赋值，调用上面的srep的构造
String::String() {
    rep = new Srep(0, "");
}

String::String(const char *p) {
    rep = new Srep(strlen(p), p);
}

/**
 * 比如现在有一个String s1("XD");
 * 那是调用了上面的构造函数，但是如果有一个
 * String s2 = s1;
 * 这里虽然是等于号，但是本质还是构造函数
 * 现在来解释为什么String里面要有一个Srep而不算是直接用char*
 * 看上面的例子，s1是创建一个rep，里面有str, size, ref_num，
 * 分别为"XD", 2, 1
 * 但是当下面s2来的时候，不再创建一个rep，而是直接++ref_num，
 * 并且让s2的ref和s1的ref指向同一片内存。这样就能节省空间了
 */
String::String(const String &other) {
    ++(other.rep->ref_num);
    this->rep = other.rep;
}

/**
 * 如果不是String s2 = s1，而是s2 = s1，才走的是这里
 * 所以这是赋值运算，而不是构造函数
 */
String &String::operator=(const String &other) {
    //还是把other的引用计数+1
    ++(other.rep->ref_num);

    //如果s2的引用值少了一个变成了0，那就表示没人用，删掉
    if(--(rep->ref_num) == 0) delete rep;

    this->rep = other.rep;

    return *this;
}

String &String::operator=(const char *other) {
    rep = new Srep(strlen(other), other);
    return *this;
}

String &String::operator+=(const String &other) {
    char* tail = rep->str + rep->size;
    rep->size += other.rep->size;
    --rep->size;
    strcpy(tail, other.rep->str);
    return *this;
}

/**
 * 当调用类似char c1 = s[1];的时候(s是String)，
 * 首先进入此函数，返回一个Cref类型的变量，构造函数使用this和i
 * 代表我要返回的就是本身，即s，然后和我要访问的下标。那这乍一看不是等于
 * 啥也没返回吗？注意等号左边，是一个char，所以还要进行一个强制转换，怎么转的呢？
 * 我们动手脚就是在operator char()函数里
 * 另外，这里本来是要报错的：Calling a private constructor of class 'String::Cref'
 * 没报错就是因为我们在Cref里加了一个friend，这个函数是属于String的，本来没有资格访问
 * Cref的私有函数，但是我们声明了"String是我Cref的好朋友"，所以才能够访问
 *
 * 另外，如果调用类似s[1] = 'a'的时候
 * 也是会进入此函数，不过发现String在等号左边，
 * 就会调用operator=函数，来运算符重载，也是在里面确定改的位数
 */
String::Cref String::operator[](int i) {
    return Cref(*this, i);
}

/**
 * 在上面函数return Cref的时候，自动会调用此函数，条件是要赋值给一个char类型的
 * 在这里我们能拿到整个字符串，以及需要访问的下标。也就是说其实只需要一个char
 * 动手脚就是在这里，我们调用内置的访问函数read来返回
 */
String::Cref::operator char() const {
    return s.read(i);
}

/**
 * 在上面的函数return Cref的时候，会自动调用此函数，条件是要将一个char类型赋值给我
 * 在这里我们能拿到整个字符串，以及需要访问的下标。也就是说其实只需要一个char
 * 动手脚就是在这里，我们调用内置的访问函数write来改写
 */
void String::Cref::operator=(char c) {
    s.write(i, c);
}


//在这里实现定义就不会报错
char String::read(int i) const {
    return rep->str[i];
}

void String::write(int i, char ch) {
    rep->str[i] = ch;
}

/**
 * 现在有一个const String s2;
 * 如果要给s2赋值的话，一定是报错的，但是读没问题，这就和非const对象有区别
 * 所以在这里单独区分出来，只能读不能写，也就不用通过Cref来操作了
 */
char String::operator[](int i) const {
    return rep->str[i];
}

ostream &operator<<(ostream &os, const String &s) {
    if(s.rep != nullptr && s.rep->str != nullptr)
        os << s.rep->str;
    return os;
}




